Introduction
For design reasons the OpenGL specification was isolated from any
window system dependencies. The resulting interface is a portable,
streamlined and efficient 2D and 3D rendering library. It is up to
the native window system to open and render windows. The OpenGL
library communicates with the native system through additional
auxiliary libraries. For example, the GLX auxiliary library
describes the interaction between OpenGL and the X window
System.
The OpenGL Utility Toolkit (GLUT) is a programming interface with
ANSI C and FORTRAN bindings for writing window system independent
OpenGL programs. It was written by Mark J. Kilgard and covers a
great hole left by the OpenGL specification. Thanks to GLUT
developers we can use a common window system interface independently
of the target platform. OpenGL applications using GLUT can be easily
ported between platforms without having to introduce numerous
changes to the source code. GLUT definitely simplifies the
production of OpenGL code and it complements the OpenGL
library.
GLUT is relatively small and easy to learn. It is well designed
and in fact, its author has already written wonderful documentation
for it. Therefore starting a series of articles here in
LinuxFocus seems redundant. We
encourage any serious developer to read Mark's documentation. Our
purpose for writing this regular GLUT column is to introduce the
GLUT library and its usage step by step with examples as a companion
reading with the OpenGL series of this magazine. We hope this
will make a useful contribution and motivate more programmers
to join the OpenGL-linux wagon. In any case, get your own copy
of Mark's documentation as a good reference.
The GLUT API is a state machine like OpenGL. This means that GLUT
has a number of state variables that live during the execution of
the application. The initial states of the GLUT machine has been
reasonably chosen to fit most applications. The program can modify
the values of the state variables as it sees fit. Whenever a GLUT
function is invoked its action is modified according to the values
of the state variables. GLUT functions are simple, they take few
parameters. No pointers are returned and the only pointers passed to
GLUT functions are pointers to character strings and opaque font
handles.
GLUT functions can be classified into several sub-APIs according
to their functionality:
- Initialization
- Beginning Event Processing
- Window Management
- Overlay Management
- Menu Management
- Callback registration
- Color Index Colormap Management
- State Retrieval
- Font Rendering
- Geometric Shape Rendering
In this article
we will explore some of the initialization, event processing and
window management functions necessary to start a simple OpenGL
program.
Initializations
Every OpenGL program using GLUT must begin by initializing the
GLUT state machine. The glut initialization functions are prefixed
by glutInit-. The main initialization routine is
glutInit: Usage:
glutInit(int **argcp, char
**argv); argcp is a pointer to the program's
unmodified argc variable from main. Upon return, the value pointed
to by argcp is updated because glutInit extracts any command line
options relevant for the GLUT library, for example: under the X
Window System environment, any options relevant for the X window
associated to the GLUT window. argv is the program's
unmodified argv variable for main.
glutInit takes care of initializing the GLUT state
variables and negotiating a session with the window system. There
are a few routines that could appear before glutInit; only
routines prefixed by glutInit-. These routines can be used
to set the default window initialization state. For
example: Usage:
glutInitWindowPosition(int x, int **y);
glutInitWindowSize(int width, int **height); x,y = screen position in pixels of the window (upper left
corner) width,height in pixels
of the window.
There is another initialization routine omni-present in every
OpenGL application, glutInitDisplayMode():
Usage:
glutInitDisplayMode(unsigned int mode); mode is the Display mode, a
bitwise OR-ing of GLUT display mode bit masks. The possible bitmask
values are:
| GLUT_RGBA |
Select an RGBA mode window. This is the
default if neither GLUT_RGBA nor GLUT_INDEX are
specified. |
| GLUT_RGB |
same as GLUT_RGBA. |
| GLUT_INDEX |
Select color index window mode. This
overrides GLUT_RGBA. |
| GLUT_SINGLE |
Select a single buffered window. This is the
default. |
| GLUT_DOUBLE |
Select a double buffered window. This
overrides GLUT_SINGLE. |
| GLUT_ACCUM |
Select a window with an accumulation
buffer. |
| GLUT_ALPHA |
Select a window with an alpha component to
the color buffer(s). |
| GLUT_DEPTH |
Select a window with a depth
buffer. |
| GLUT_STENCIL |
Select a window with a stencil
buffer. |
| GLUT_MULTISAMPLE |
Select a window with multismapling
support. |
| GLUT_STEREO |
Select a stereo window. |
| GLUT_LUMINANCE |
Select a stereo window with a "luminance"
color model. | If some of these
features are not familiar to you, don't worry., sooner or later we
will write about them. Let us examine a couple of examples.
First a simple initialization for one shot rendering
application:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Set window size and location */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Select type of Display mode:
Single buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
/* Initialize GLUT state */
glutInit(&argcp, argv);
.....more code
};
Second an example of an animation program:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Set window size and location */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Select type of Display mode:
Double buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Initialize GLUT state */
glutInit(&argcp, argv);
.....more code
};
We will come back to these two examples as we continue to learn
more about GLUT. The main difference is that in the second case the
display is initialized in a double buffer mode, ideal for animations
because it eliminates flickering effects while changing frames in
the animation sequence.
Event
Processing
As mentioned before, GLUT is a state machine. Now we will learn
it is also designed as an event driven engine. This means that there
is a "timer" or continuous loop that gets started after the proper
initializations and that processes, one by one, all the events
declared to GLUT during initialization. Events are: a mouse being
clicked, a window closed, a window reshape, a cursor moved, keyboard
keys pressed, and even more curiously the "idle" event, i.e. nothing
happens! Each one of the possible events must be registered in
one of the GLUT state variables for the "timer" or event processing
loop of GLUT to periodically check whether that event has been
triggered by the user. For example, we could
register "click mouse button" as an event for GLUT to watch out for.
Events are registered through callback registration routines.
All have the syntax glut[someEvent]Func, in the case of the
mouse clicking it would be glutMouseFunc. A callback
registration tells the GLUT engine which user-defined function is to
be called if the corresponding event is triggered. So, if I write my
own routine MyMouse which specifies what to do if the left
mouse button is clicked, (or the right, etc.) then I can register my
callback function after the glutInit() in main() using the
statement "glutMouseFunc(MyMouse);" .
Let us leave for later which callback functions and events are
permitted in GLUT. The important thing now is that after all the
important events in our application have been registered we must
invoke the event processing routine of GLUT, namely
glutMainLoop(). The function never comes back, our program
basically enters an infinite loop. It will call as necessary any
callbacks that have been previously registered. Every
main() for an OpenGL application must then end in a
glutMainLoop() statement. So in the case of our animation
template:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Initialize GLUT state */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Open a window */ glutCreateWindow("My
OpenGL Application");
/* Select type of Display mode:
Double buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Register Callback Functions */
.....
/* Start Event Processing Engine */
glutMainLoop(); };
Notice I have added some extra code we never mentioned before. It
is one of GLUT's window management routines,
glutCreateWindow(char **name). This is what I like so much
about OpenGL & GLUT design philosophy, it is pretty clear what
the routine does by just looking at the name!. It also takes care of
actually passing the order to the underlying window system to open a
window for our OpenGL application. The window will have the name
"name" passed as a character string. In the X Window Environment
this name is written on the upper left corner of the window. The
window management section of GLUT has many other functions that we
will eventually have a look at. For now, this one is sufficient. I
have also rearranged the initialization routines to show that they
can be placed after glutInit().
Back to events... I want now to introduce two callback
registration functions that are very fundamental in any animation
program. The glutDisplayFunc which sets the display
function for the current window and the glutIdleFunc which
sets the idle callback. Both registration routines expect a function
of type void *(void). Say we write two additional callback
functions to our animation template, void MyDisplay(void)
which takes care of invoking the OpenGL instructions that actually
draw our scene onto the window, and void MyIdle(void) which
is a function that gets called whenever there is no other user
input, that is, each time the event processing machine of GLUT goes
once around the infinite loop (glutMainLoop()) and does not
find any new event triggered, it processes MyIdle. Why do I
need to register an Idle callback function in an animation program?
Because if we wish to modify each one of the images (frames) shown
during the animation independently of any user input, there has to
be a function (the idle callback function) that gets called every so
often during the life of the OpenGL program and changes the
frames before they get drawn by Mydisplay().
Animation
Example
Finally here is a simple template for an animation program:
#include <GL/glut.h>
void MyIdle(void){ /* Some code to modify
the variables defining next frame */
.... };
void MyDisplay(void){ /* Some OpenGL code
that draws a frame */ .... /*
After drawing the frame we swap the buffers */
glutSwapBuffers(); };
void main(int argcp, char **argv){
/* Initialize GLUT state */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Open a window */ glutCreateWindow("My
OpenGL Application");
/* Select type of Display mode:
Double buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Register Callback Functions */
glutDisplayFunc(MyDisplay)
glutIdleFunc(MyIdle)
/* Start Event Processing Engine */
glutMainLoop(); };
Notice that at the end of MyDisplay I have added a new
GLUT routine, glutSwapBuffers(). This is very useful in
animations. We are using a window in DOUBLE buffer mode, one shown
and one hidden. The drawing OpenGL instructions in this case always
render into the hidden buffer. The glutSwapBuffers call,
exchanges the buffers, showing in the window at once what was drawn.
This technique is common in computer animations because it prevents
the human eye from seeing the frame being constructed line by
line.
There is already enough material to start writing OpenGL
applications. The only things missing are the OpenGL
instructions in MyDisplay that actually do the
drawing...but that is another story ;-).
In the next article on GLUT programming we will explore in more
depth the functionality available to us in the Window Management
section of GLUT, and how to open multiple scenes inside the same
window. We will also learn about using menus, including the
pros and cons for their portability. |